package com.easy.project.aspect;

import com.alibaba.fastjson2.JSON;
import com.easy.project.domain.vo.common.ApiLogVO;
import lombok.extern.slf4j.Slf4j;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.AfterThrowing;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Pointcut;
import org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor;
import org.springframework.stereotype.Component;
import org.springframework.web.multipart.MultipartFile;

import javax.annotation.Resource;
import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
import javax.servlet.http.HttpServletRequest;
import java.util.ArrayList;
import java.util.Calendar;
import java.util.List;

/**
 * api 日志记录
 */
@Aspect
@Component
@Slf4j
public class ApiLogAspect {

    @Resource
    private ThreadPoolTaskExecutor apiLogTaskExecutor;

    /**
     * 使用的代理对象获取 保证线程安全
     */
    @Resource
    private HttpServletRequest httpServletRequest;

    /**
     * 对所有的标注的请求注解进行拦截
     */
    @Pointcut("@annotation(org.springframework.web.bind.annotation.PostMapping) || @annotation(org.springframework.web.bind.annotation.GetMapping)" +
            "|| @annotation(org.springframework.web.bind.annotation.PutMapping) || @annotation(org.springframework.web.bind.annotation.DeleteMapping)")
    public void apiLogAspect() {

    }

    /**
     * 环绕通知
     *
     * @param proceedingJoinPoint
     */
    @Around(value = "apiLogAspect()")
    public Object around(ProceedingJoinPoint proceedingJoinPoint) throws Throwable {
        // 执行方法
        long startTime = Calendar.getInstance().getTimeInMillis();
        Object proceed = proceedingJoinPoint.proceed();
        long endTime = Calendar.getInstance().getTimeInMillis();

        // 方法参数
        Object[] args = proceedingJoinPoint.getArgs();
        String url = getUrl();
        String requestMethod = getRequestMethod();
        // 优化使用异步线程池去记录日志 防止记录日志出错导致业务处理失败
        apiLogTaskExecutor.execute(()->{
            long time = endTime - startTime;
            ApiLogVO build = ApiLogVO.builder()
                    .args(buildArgs(args))
                    .url(url)
                    .requestMethod(requestMethod)
                    .result(JSON.toJSONString(proceed))
                    .time(String.valueOf(time))
                    .build();
            log.info("httpLog：{}", JSON.toJSONString(build));
        });
        return proceed;
    }

    /**
     * 异常后只记录发送的参数
     */
    @AfterThrowing(value = "apiLogAspect()")
    public void afterThrowing(JoinPoint joinPoint){
        // 方法参数
        Object[] args = joinPoint.getArgs();
        String url = getUrl();
        String requestMethod = getRequestMethod();
        apiLogTaskExecutor.execute(()->{
            ApiLogVO build = ApiLogVO.builder()
                    .args(buildArgs(args))
                    .url(url)
                    .requestMethod(requestMethod)
                    .build();
            log.error("httpLog error params：{}", JSON.toJSONString(build));
        });
    }

    /**
     * 创建参数
     *
     * @param args
     * @return
     */
    private String buildArgs(Object[] args) {
        // 筛选掉不要的参数 文件上传,servlet参数
        List<Object> newArgs = new ArrayList<>();
        for (Object arg : args) {
            if (arg instanceof ServletRequest || arg instanceof ServletResponse || arg instanceof MultipartFile){
                continue;
            }
            newArgs.add(arg);
        }
        String param;
        try {
            param = JSON.toJSONString(newArgs);
        }catch (Exception e){
            log.error("api参数无法序列化：",e);
            param = "无法json序列化";
        }
        return param;
    }


    /**
     * 获取请求方式
     *
     * @return
     */
    private String getRequestMethod() {
        return httpServletRequest.getMethod();
    }

    /**
     * 获取请求地址
     *
     * @return
     */
    private String getUrl() {
        return httpServletRequest.getServletPath();
    }


}
